#import <Foundation/Foundation.h>
#import <sys/sysctl.h>
#import <objc/runtime.h>
#import <UIKit/UIApplication.h>
#import <Foundation/NSException.h>
#import <LockInfo/Plugin.h>
#import <EventKit/EventKit.h>

#ifdef DEVELOPMENT_MODE
#import "pi_todoAppDelegate.h"
#import "pi_todoViewController.h"
#import "pi_todoDetailController.h"
#endif

#import "FWCommon.h"


extern "C" CFStringRef UIDateFormatStringForFormatType(CFStringRef type);

static CGSize const kScreenSizeiPhone = CGSizeMake(320, 480);
static CGSize const kScreenSizeiPad = CGSizeMake(768, 1024);

NSInteger const kDebugModeCode = DEBUG_CODE;

NSInteger const SECONDS_IN_HOUR = 3600;
NSInteger const SECONDS_IN_MINUTE = 60;

NSNumber* const NSNumberYES = [[NSNumber alloc] initWithBool:YES];
NSNumber* const NSNumberNO = [[NSNumber alloc] initWithBool:NO];
NSNumber* const NSNumber0 = [[NSNumber alloc] initWithInt:0];
NSNumber* const NSNumber1 = [[NSNumber alloc] initWithInt:1];


CGSize FWGetCurrentScreenSize()
{
    BOOL iPad = (UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPhone);
    CGSize size = iPad ? kScreenSizeiPad : kScreenSizeiPhone;
    
    UIInterfaceOrientation orientation = [UIApplication sharedApplication].statusBarOrientation;
    if ((orientation == UIInterfaceOrientationLandscapeLeft) || (orientation == UIInterfaceOrientationLandscapeRight)) {
        CGFloat tmp = size.height;
        size.height = size.width;
        size.width = tmp;
    }
    
    DebugLog(@"LI:PICommon: Current screen size = {%f, %f}", size.width, size.height);

    return size;
}

BOOL FWIsDeviceAnIPad()
{
    return (UI_USER_INTERFACE_IDIOM() != UIUserInterfaceIdiomPhone);
}

NSString* FWBase64EncodeString(NSString* src) 
{
    static char base64[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    
    NSData* unicode = [src dataUsingEncoding:NSUTF8StringEncoding];
    
    NSMutableString *dest = [[NSMutableString alloc] initWithString:@""];
    int length = [unicode length];
    
    for (int i = 0; i < length; i += 3) {
        for (int nib = 0; nib < 4; nib++) {
            // nibble:nib from char:byt
            int byt = (nib == 0) ? 0 : nib-1;
            int ix = (nib+1) * 2;
            
            if (i+byt >= length)
                break;
            
            // extract the top bits of the nibble, if valid
            //unsigned char curr = (([src characterAtIndex:i+byt] << (8-ix)) & 0x3F);
            unsigned char curr;
            [unicode getBytes:&curr range:NSMakeRange(i+byt, 1)];

            curr = (curr << (8-ix)) & 0x3F;
            
            // extract the bottom bits of the nibble, if valid
            if (i+nib < length) {
                unsigned char extra;
                [unicode getBytes:&extra range:NSMakeRange(i+nib, 1)];
                curr |= ((extra >> ix) & 0x3F);
            }

            [dest appendFormat:@"%c", base64[curr]];
        }
    }
    
    if ((length % 3) == 1)
        [dest appendString:@"=="];
    else if ((length % 3) == 2)
        [dest appendString:@"="];
    
    return dest;
}

NSString* FWXMLEncodeString(NSString *src)
{
    NSMutableString* str = [NSMutableString stringWithString:src];
    
    [str replaceOccurrencesOfString:@"&" withString:@"&amp;" options:NSLiteralSearch range:NSMakeRange(0, [str length])];
    [str replaceOccurrencesOfString:@"<" withString:@"&lt;" options:NSLiteralSearch range:NSMakeRange(0, [str length])];
    [str replaceOccurrencesOfString:@">" withString:@"&gt;" options:NSLiteralSearch range:NSMakeRange(0, [str length])];
    [str replaceOccurrencesOfString:@"'" withString:@"&apos;" options:NSLiteralSearch range:NSMakeRange(0, [str length])];
    [str replaceOccurrencesOfString:@"\"" withString:@"&quot;" options:NSLiteralSearch range:NSMakeRange(0, [str length])];
    
    return str;
}

NSString* FWObjectDescription(NSObject* obj)
{
    unsigned int count;
    objc_property_t* properties = class_copyPropertyList([obj class], &count);
    NSMutableDictionary* dictionary = [NSMutableDictionary dictionaryWithCapacity:count];
    for (unsigned int i = 0; i < count; i++) {
        NSString *name = [NSString stringWithUTF8String:property_getName(properties[i])];
        NSString *value = [obj valueForKey:name];
        if (value != nil) {
            [dictionary setObject:value forKey:name];
        }
    }

    free(properties);
    return [dictionary description];
}

NSString* FWUFT8StringForCharString(const char* value)
{
    return [NSString stringWithUTF8String:(value ? value : "")];
}

BOOL FWIsColorDark(UIColor *color)
{
    if (!color)
        return YES;
    
    const CGFloat *componentColors = CGColorGetComponents(color.CGColor);
    
    CGFloat colorBrightness = ((componentColors[0] * 299) + (componentColors[1] * 587) + (componentColors[2] * 114)) / 1000;
//    CGFloat colorBrightness = ((componentColors[0] * 147) + (componentColors[1] * 286) + (componentColors[2] * 56)) / 1000;
    if (colorBrightness < 0.5)
        return YES;
    else
        return NO;
}

NSString* FWStringValueFromData(NSData* data)
{
    if (!data)
        return nil;
    return [[[NSString alloc] initWithData:data encoding:NSASCIIStringEncoding] autorelease];
}

NSNumber* FWNumberValueFromData(NSData* data)
{
    if (!data)
        return nil;
    return [NSPropertyListSerialization propertyListWithData:data options:0 format:NULL error:NULL];
}

NSString* FWLocalise(NSBundle* bundle, NSString* str)
{
#ifndef DEVELOPMENT_MODE
    return [bundle localizedStringForKey:str value:str table:nil];
#else
    NSString* result = [bundle localizedStringForKey:str value:str table:nil];
    if ([result isEqualToString:str])
        return [NSString stringWithFormat:@"^%@", str];
    else
        return [NSString stringWithFormat:@"!%@", result];
#endif
}


UIView* FWSuperViewOfClass(UIView* view, Class claz)
{
    while (view != nil) {
        if ([view isMemberOfClass:claz])
            break;
        view = view.superview;
    }
    
    return view;
}

NSNumber* FWNewNumberWithInt(int i)
{
    return [[NSNumber alloc] initWithInt:i];
}

static id const kLILabelClass = objc_getClass("LILabel");
LILabel* FWLILabel(CGRect rect)
{
    return [[[kLILabelClass alloc] initWithFrame:rect] autorelease];
}

static id const kLISectionInfoClass = objc_getClass("LISectionInfo");
LISectionInfo* FWLISectionInfo()
{
    return [[[kLISectionInfoClass alloc] init] autorelease];
}

static id const kLIDataClass = objc_getClass("LIData");
LIData* FWLIData(NSString* key, NSDictionary* context)
{
    LIData* data = [[[kLIDataClass alloc] init] autorelease];
    data.uniqueID = key;
    data.context = context;
    return data;
}

static id const kLIActionClass = objc_getClass("LIAction");
LIAction* FWLIAction(id target, SEL sel)
{
    LIAction* action = [[[kLIActionClass alloc] init] autorelease];
    
    action.target = target;
    action.selector = sel;
    
    return action;
}


static id const kLockInfoControllerClass = objc_getClass("LockInfoController");
UIView* FWLITableView()
{
#ifdef DEVELOPMENT_MODE
    pi_todoAppDelegate* appDelegate = (pi_todoAppDelegate*)[[UIApplication sharedApplication] delegate];
    pi_todoViewController* view = [appDelegate.navController.viewControllers objectAtIndex:0];
    return view.view;
#endif
    
    id liController = [kLockInfoControllerClass sharedInstance];
    if ([liController respondsToSelector:@selector(tableViewController)]) {
        id liTableViewController = [liController tableViewController];
        if ([liTableViewController respondsToSelector:@selector(tableView)]) {
            return [liTableViewController tableView];
        }
    }
    
    return nil;
}


NSString* FWGetSysInfoValue(const char* name)
{
    size_t size;
    sysctlbyname(name, NULL, &size, NULL, 0);
    
    char *answer = (char*)malloc(size);
    sysctlbyname(name, answer, &size, NULL, 0);
    
    NSString *results = [NSString stringWithCString:answer encoding: NSUTF8StringEncoding];
    
    free(answer);
    return results;
}

NSString* FWGetPlatformType()
{
    NSString* platform = FWGetSysInfoValue("hw.machine");
    
    if ([platform length] > 0) {
        platform = [platform uppercaseString];
        if ([platform hasPrefix:@"IPHONE"])
            return @"iPhone";
        else if ([platform hasPrefix:@"IPAD"])
            return @"iPad";
        else if ([platform hasPrefix:@"IPOD"])
            return @"iPod";
    }
    
    return platform;
}


NSString* FWKeyForCalendar(EKCalendar* calendar)
{
    if ([calendar respondsToSelector:@selector(calendarIdentifier)])
        return [calendar calendarIdentifier];
    
    if ([calendar respondsToSelector:@selector(accountName)])
        return [NSString stringWithFormat:@"%@;%@", calendar.title, [calendar accountName]];
    
    return calendar.title;
}

NSInteger FWKeyFromRefID(NSString* refId)
{
    // RefIDs look like: "x-coredata://745150F3-B5CE-4D10-8904-1EAC9C83ADFD/PIContext/p8"
    if (![refId hasPrefix:@"x-coredata://"])
        return 0;

    NSRange prefix = [refId rangeOfString:@"x-coredata://.*/.*/[a-z]" options:NSRegularExpressionSearch];
    if (prefix.location == NSNotFound)
        return 0;
    
    NSString* idNumber = [refId substringFromIndex:prefix.length];
    return [idNumber integerValue];
}


// Returns the UTC equivalent of a local date. So if the local date is 01/01/2001+001
// then returns 01/01/20001+000
NSDate* FWUTCForLocalDate(NSDate* date)
{
    if (date.isNull)
        return date;
    
    // Get seconds from UTC for the required date
    NSInteger localOffset = [[NSTimeZone systemTimeZone] secondsFromGMTForDate:date];
    
    // Return the UTC time that is equivalent to the local time
    return [date addTimeInterval:localOffset];
}

FWWeekdayName FWLocalWeekdayName(NSDate* date)
{
    NSCalendar* cal = [NSCalendar currentCalendar];
    cal.timeZone = [NSTimeZone systemTimeZone];
    
    int mask = NSWeekdayCalendarUnit;
    NSDateComponents* comps = [cal components:mask fromDate:date];
    
    if ([cal maximumRangeOfUnit:NSWeekdayCalendarUnit].length != 7)
        return FWWeekdayNameUnknown;

    if (comps.weekday == 1)
        return FWWeekdayNameSunday;
    else
        return (FWWeekdayName)(comps.weekday - 1);
}

BOOL FWIsLocalDateWeekend(NSDate* date, FWWeekdayName weekendStart)
{
    if (weekendStart == FWWeekdayNameUnknown)
        return NO;
    
    return FWIsDayWeekend(FWLocalWeekdayName(date), weekendStart);
}

BOOL FWIsDayWeekend(FWWeekdayName day, FWWeekdayName weekendStart)
{
    if (weekendStart == FWWeekdayNameUnknown)
        return NO;
    
    return (day == weekendStart) || (day == (weekendStart + 1));
}


FWWeekdayName FWRecurranceWeekdayName(int day)
{
    // System days are Sun(1)-Sat(7)
    // Local days are Mon(1)-Sun(7) Unknown(0)
    FWWeekdayName name = (FWWeekdayName)(day - 1);
    if (name == FWWeekdayNameUnknown)
        name = FWWeekdayNameSunday;
    
    return name;
}


// Returns the locl equivalent of a UTC date. So if the UTC date is 02/01/2001 00:00+00:00
// and the local timezone is 3 hours ahead then returns 02/01/20001 00:00:00+03:00 or
// 01/01/2001 21:00+00:00
NSDate* FWLocalDateForUTC(NSDate* date)
{
    if (date.isNull)
        return date;

    // Get seconds from UTC for the required date
    NSInteger localOffset = [[NSTimeZone systemTimeZone] secondsFromGMTForDate:date];
    
    // Return the time that is equivalent to the UTC time once the
    // local timezone has been applied
    return [date addTimeInterval:-localOffset];
}

NSDate* FWLocalDateForLocalMidnightOnDate(NSDate* date)
{
    NSCalendar* cal = [NSCalendar currentCalendar];
    int mask = (NSDayCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit);
    
    cal.timeZone = [NSTimeZone systemTimeZone];
    NSDateComponents* components = [cal components:mask fromDate:date];
    
#ifdef DEVELOPMENT_MODE
    NSInteger day = components.day;
    NSInteger month = components.month;
    NSInteger year = components.year;
    //XLog(@"Date = %i/%i/%i", day, month, year);
#endif
    
    NSDate* midnight = [cal dateFromComponents:components];
    return midnight;
}

NSDate* FWLocalDateForLocalMidnight()
{
    return FWLocalDateForLocalMidnightOnDate([NSDate date]);
}

BOOL FWIsLocalDateSameWeek(NSDate* date1, NSDate* date2)
{
    NSCalendar* cal = [NSCalendar currentCalendar];
    cal.timeZone = [NSTimeZone systemTimeZone];
    
    int mask = (NSWeekCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit);
    NSDateComponents* comps1 = [cal components:mask fromDate:date1];
    NSDateComponents* comps2 = [cal components:mask fromDate:date2];

#ifdef DEVELOPMENT_MODE
    NSInteger week1 = comps1.week;
    NSInteger month1 = comps1.month;
    NSInteger year1 = comps1.year;
    //XLog(@"Date 1 = %i/%i/%i", week1, month1, year1);
    
    NSInteger week2 = comps2.week;
    NSInteger month2 = comps2.month;
    NSInteger year2 = comps2.year;
    //XLog(@"Date 2 = %i/%i/%i", week2, month2, year2);
#endif
    
    return (comps1.week == comps2.week && comps1.month == comps2.month && comps1.year == comps2.year);
}

BOOL FWIsLocalDateSameDay(NSDate* date1, NSDate* date2)
{
    NSCalendar* cal = [NSCalendar currentCalendar];
    cal.timeZone = [NSTimeZone systemTimeZone];

    int mask = (NSDayCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit);
    NSDateComponents* comps1 = [cal components:mask fromDate:date1];
    NSDateComponents* comps2 = [cal components:mask fromDate:date2];
    
#ifdef DEVELOPMENT_MODE
    NSInteger day1 = comps1.day;
    NSInteger month1 = comps1.month;
    NSInteger year1 = comps1.year;
    //XLog(@"Date 1 = %i/%i/%i", day1, month1, year1);

    NSInteger day2 = comps2.day;
    NSInteger month2 = comps2.month;
    NSInteger year2 = comps2.year;
    //XLog(@"Date 2 = %i/%i/%i", day2, month2, year2);
#endif
    
    return (comps1.day == comps2.day && comps1.month == comps2.month && comps1.year == comps2.year);
}

BOOL FWIsLocalDateThisWeek(NSDate* date)
{
    return FWIsLocalDateSameWeek(date, [NSDate date]);
}

BOOL FWIsLocalDateToday(NSDate* date)
{
    return FWIsLocalDateSameDay(date, [NSDate date]);
}

BOOL FWIsLocalDateTomorrow(NSDate* date)
{
    return FWIsLocalDateSameDay(date, [NSDate dateWithTimeIntervalSinceNow:SECONDS_PER_DAY]);
}

BOOL FWIsLocalDateYesterday(NSDate* date)
{
    return FWIsLocalDateSameDay(date, [NSDate dateWithTimeIntervalSinceNow:-SECONDS_PER_DAY]);
}

NSInteger FWSecondsWithinLocalDate(NSDate* date)
{
    NSCalendar* cal = [NSCalendar currentCalendar];
    cal.timeZone = [NSTimeZone systemTimeZone];
    
    int mask = (NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit);
    NSDateComponents* comps = [cal components:mask fromDate:date];

#ifdef DEVELOPMENT_MODE    
    NSInteger hour = comps.hour;
    NSInteger mins = comps.minute;
    NSInteger secs = comps.second;
    //XLog(@"Time %@ = %i:%i:%i", date, hour, mins, secs);
#endif
    
    return comps.hour * SECONDS_IN_HOUR + comps.minute * SECONDS_IN_MINUTE + comps.second;
}


static NSString* const kNullSymbol = @"\u2205"; // Empty set symbol
static NSTimeInterval const kSecondsNoYearInterval = SECONDS_PER_DAY * 90;
static NSTimeInterval const kSecondsDayNameInterval = SECONDS_PER_DAY * 4;

NSString* FWLocalDateStringForUTCDate(NSDate* date, FWDateFormatterFormat format, BOOL time, NSBundle* bundle)
{
    NSString* result = kNullSymbol;
    
    if (format == FWDateFormatterTimeOnlyFormat)
        time = YES;
    
    if (date && !date.isNull) {
        NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
        dateFormatter.timeZone = [NSTimeZone systemTimeZone];

        if (format == FWDateFormatterLongFormat) {
            NSTimeInterval interval = [date timeIntervalSinceNow];
            if ((interval > 0) && (interval < kSecondsDayNameInterval)) {
                // Use long day name only
                dateFormatter.dateFormat = @"EEEE";
                format = FWDateFormatterNormalFormat;
            }
            else if (abs(interval) <= kSecondsNoYearInterval) {
                // Generate a date format without the year
                dateFormatter.dateFormat = [NSDateFormatter dateFormatFromTemplate:@"d MMMM" options:0 locale:[NSLocale currentLocale]];
            }
            else {
                // Use long date format - eg "November 23, 1937"
                dateFormatter.dateStyle = NSDateFormatterLongStyle;
                dateFormatter.timeStyle = NSDateFormatterNoStyle;
            }
        }
        else if (format == FWDateFormatterLongAbbreviatedFormat) {
            NSTimeInterval interval = [date timeIntervalSinceNow];
            if (((interval > 0) && (interval < kSecondsDayNameInterval)) || FWIsLocalDateToday(date)) {
                // Use long day name only
                dateFormatter.dateFormat = @"EEE";
                format = FWDateFormatterCompressedFormat;
            }
            else if (abs(interval) <= kSecondsNoYearInterval) {
                // Generate a date format without the year
                dateFormatter.dateFormat = [NSDateFormatter dateFormatFromTemplate:@"EEE d MMM" options:0 locale:[NSLocale currentLocale]];
                format = FWDateFormatterCompressedFormat;
            }
            else
                // Use long date format - eg "November 23, 1937"
                dateFormatter.dateFormat = (NSString*)UIDateFormatStringForFormatType(CFSTR("UIAbbreviatedWeekdayDateFormat"));
        }
        else if (format == FWDateFormatterCompressedFormat) {
            // Use shortest date format - eg "11/23/2010"
            dateFormatter.dateStyle = NSDateFormatterShortStyle;
            dateFormatter.timeStyle = NSDateFormatterNoStyle;
            
            // Change 4 digit year to 2 to make shorted - eg "11/23/10"
            dateFormatter.dateFormat = [dateFormatter.dateFormat stringByReplacingOccurrencesOfString:@"yyyy" withString:@"yy"];
        }
        else
            // Use standard date format - eg "Mon 23 Jun"
            dateFormatter.dateFormat = (NSString*)UIDateFormatStringForFormatType(CFSTR("UIAbbreviatedWeekdayMonthDayFormat"));
        
        if (format == FWDateFormatterTimeOnlyFormat) {
            result = @"";
        }
        else if (format == FWDateFormatterCompressedFormat) {
            result = [dateFormatter stringFromDate:date];
        }
        else {
            // Use relative dates if suitable
            if (FWIsLocalDateToday(date))
                result = localize(bundle, @"Date/Today");
            else if (FWIsLocalDateTomorrow(date))
                result = localize(bundle, @"Date/Tomorrow");
            else if (FWIsLocalDateYesterday(date))
                result = localize(bundle, @"Date/Yesterday");
            else {
                result = [dateFormatter stringFromDate:date];
                
                // If using long format then append the day name, eg "Mon, November 23, 1937"
                if (format == FWDateFormatterLongFormat) {
                    NSString* replace = localize(bundle, @"Date/Long Format");
                    dateFormatter.dateFormat = @"ccc";

                    replace = [replace stringByReplacingOccurrencesOfString:@"%DAY%" withString:[dateFormatter stringFromDate:date]];
                    result = [replace stringByReplacingOccurrencesOfString:@"%DATE%" withString:result];
                }
            }
        }
        
        // Add the time if provided
        if (time) {
            dateFormatter.dateStyle = NSDateFormatterNoStyle;
            dateFormatter.timeStyle = NSDateFormatterShortStyle;
            
            if ([result length] > 0)
                result = [NSString stringWithFormat:@"%@ %@", result, [dateFormatter stringFromDate:date]];
            else
                result = [dateFormatter stringFromDate:date];
        }
        
        [dateFormatter release];
        
        //DebugLog(@"LI:PITask: formatting time num=%F Local=%@ Time=%@", dateNum.doubleValue, localDate, result);
    }
    
    return result;
}

NSString* FWDebugTimeStringForUTCDate(NSDate* date)
{
    NSString* result = kNullSymbol;
    
    //if (date && !date.isNull) {
    if (!date)
        date = [NSDate nullDate];
    
        NSDateFormatter* dateFormatter = [[NSDateFormatter alloc] init];
        dateFormatter.timeZone = [NSTimeZone systemTimeZone];
        dateFormatter.dateFormat = @"dd/MM/yy HH:mm:ss";
        
        result = [dateFormatter stringFromDate:date];
        
        [dateFormatter release];
//    }
//    
    return result;
}


#ifdef DEVELOPMENT_MODE
void FWTestDateFunctions()
{
    NSCalendar* cal = [NSCalendar currentCalendar];
    cal.timeZone = [NSTimeZone timeZoneWithName:@"UTC"];
    NSDateComponents *comps1 = [[NSDateComponents alloc] init];
    [comps1 setYear:2011];
    [comps1 setMonth:8];
    [comps1 setDay:2];
    [comps1 setHour:23];
    [comps1 setMinute:30];
    [comps1 setSecond:0];
    NSDate *date1 = [cal dateFromComponents:comps1];
    [comps1 release];

    NSDateComponents *comps2 = [[NSDateComponents alloc] init];
    [comps2 setYear:2011];
    [comps2 setMonth:8];
    [comps2 setDay:3];
    [comps2 setHour:20];
    [comps2 setMinute:30];
    [comps2 setSecond:0];
    NSDate *date2 = [cal dateFromComponents:comps1];
    [comps2 release];

    XLog(@"Test1 = %@", date1);
    XLog(@"Test2 = %@", date2);

//    XLog(@"UTC for Local midnight = %@", FWUTCForLocalMidnight());
//
//    XLog(@"UTC for LocalDate = %@", FWUTCForLocalDate(date1));
//    XLog(@"Local Date for UTC = %@", FWLocalDateForUTC(date1));
//
//    XLog(@"Is Today for local timezone = %i", FWIsLocalDateToday(date1));
//    XLog(@"Is Yesterday for local timezone = %i", FWIsLocalDateYesterday(date1));
//    XLog(@"Is Tomorrow for local timezone = %i", FWIsLocalDateTomorrow(date1));
//
//    XLog(@"Is same day for local timezone = %i", FWIsLocalDateSameDay(date1, date2));
}
#endif


@implementation NSMutableString (FWCommon)

- (void)appendString:(NSString*)str withSeparator:(NSString*)sep
{
    if (self.length > 0)
        [self appendString:sep];
    [self appendString:str];
}

@end


@implementation NSString (FWCommon)

- (NSString*)stringValue
{
    return [self description];
}

@end


static const NSTimeInterval kNullTime = DBL_MIN;

@implementation NSDate (FWCommon)

+ (NSDate*)nullDate
{
    return [NSDate dateWithTimeIntervalSinceReferenceDate:kNullTime];
}

+ (NSDate*)nullableDateWithTimeIntervalSinceReferenceDate:(NSTimeInterval)interval
{
    if (interval == 0)
        return [NSDate nullDate];
    else
        return [NSDate dateWithTimeIntervalSinceReferenceDate:interval];
}

- (BOOL)isNull
{
    return self.timeIntervalSinceReferenceDate == kNullTime;
}

- (BOOL)isEarlierThanDate:(NSDate*)other
{
    if ([self isNull] && ![other isNull])
        return YES;
    else if (![self isNull] && [other isNull])
        return NO;
    
    NSTimeInterval t1 = [self timeIntervalSinceReferenceDate];
    NSTimeInterval t2 = [other timeIntervalSinceReferenceDate];
    return (long)t1 < (long)t2;
}

- (BOOL)isLaterThanDate:(NSDate*)other
{
    if ([self isNull] && ![other isNull])
        return NO;
    else if (![self isNull] && [other isNull])
        return YES;

    NSTimeInterval t1 = [self timeIntervalSinceReferenceDate];
    NSTimeInterval t2 = [other timeIntervalSinceReferenceDate];
    return (long)t1 > (long)t2;
}

- (BOOL)isEqualOrEarlierThanDate:(NSDate*)other
{
    if ([self isNull] && ![other isNull])
        return YES;
    else if (![self isNull] && [other isNull])
        return NO;
    
    NSTimeInterval t1 = [self timeIntervalSinceReferenceDate];
    NSTimeInterval t2 = [other timeIntervalSinceReferenceDate];
    return (long)t1 <= (long)t2;
}

- (BOOL)isEqualOrLaterThanDate:(NSDate*)other
{
    if ([self isNull] && ![other isNull])
        return NO;
    else if (![self isNull] && [other isNull])
        return YES;
    
    NSTimeInterval t1 = [self timeIntervalSinceReferenceDate];
    NSTimeInterval t2 = [other timeIntervalSinceReferenceDate];
    return (long)t1 >= (long)t2;
}

#ifdef DEVELOPMENT_MODE

+ (NSDate*)dateTodayAtHour:(NSInteger)hour minute:(NSInteger)minute section:(NSInteger)second
{
    NSCalendar* cal = [NSCalendar currentCalendar];
    cal.timeZone = [NSTimeZone systemTimeZone];
    int mask = (NSDayCalendarUnit | NSMonthCalendarUnit | NSYearCalendarUnit | NSHourCalendarUnit | NSMinuteCalendarUnit | NSSecondCalendarUnit);

    NSDateComponents* comps = [cal components:mask fromDate:[NSDate date]];
    [comps setHour:hour];
    [comps setMinute:minute];
    [comps setSecond:second];

    return [cal dateFromComponents:comps];
}

#endif


void _XYLog(CFAbsoluteTime *lastTime, NSString *format, va_list argList) {
    
	CFAbsoluteTime time = CFAbsoluteTimeGetCurrent();
	static unsigned logcount = 0;
	//if (logcount++ % 100 == 0) XLog(@"logcount: %i", logcount);
	static CFTimeZoneRef zone = nil;
	if (!zone) zone = CFTimeZoneCopyDefault();
	
	CFGregorianDate date = CFAbsoluteTimeGetGregorianDate(time, zone);
	if (lastTime) {
		double elapsed_time = time - *lastTime;
		unsigned total_sec = elapsed_time;
		double fraction = elapsed_time - (double)total_sec;
		unsigned milli = fraction * 1000.0f;
		unsigned micro = fraction * 1000000.0f;
		micro %= 1000;
		// log elapsed time [sec.ms_us|pid]
		printf("[%03i.%03i_%03i|%i] ", total_sec, milli, micro, getpid());
	} else {
		unsigned sec = date.second;
		double fraction = date.second - (double)sec;
		unsigned milli = fraction * 1000.0f;
		// log standard time [hours:min:sec.ms|pid]
		printf("[%02i:%02i:%02i.%03i|%i] ", date.hour, date.minute, sec, milli, getpid());
	}
    
	CFStringRef log = CFStringCreateWithFormatAndArguments(NULL, NULL, (CFStringRef)format, argList);
	char *ptr = (char *)CFStringGetCStringPtr(log, kCFStringEncodingUTF8);
	if (ptr) 	
		printf("%s\n", ptr);
	else {
		unsigned buflen = CFStringGetLength(log) * 4;
		ptr = (char *)malloc(buflen);
		if (CFStringGetCString(log, ptr, buflen, kCFStringEncodingUTF8));
        printf("%s\n", ptr);
		free(ptr);
	}
	CFRelease(log);
    
    
}

void XYLog(NSString *format, ...) {
    
	va_list argList;
	va_start(argList, format);
	_XYLog(nil, format, argList);
	va_end(argList);
    
}

void XYTimeLog(CFAbsoluteTime *time, NSString *format, ...) {
	
	va_list argList;
	va_start(argList, format);
	_XYLog(time, format, argList);
	va_end(argList);
    
	if (time) *time = CFAbsoluteTimeGetCurrent();
    
}

@end
